package cn.iocoder.yudao.module.scrm.service.clue;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.strategy.DictTenantContext;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.scrm.annotations.EncryptPhoneNumbers;
import cn.iocoder.yudao.module.scrm.controller.admin.clue.vo.*;
import cn.iocoder.yudao.module.scrm.dal.dataobject.clue.ClueDO;
import cn.iocoder.yudao.module.scrm.dal.dataobject.customer.CustomerDO;
import cn.iocoder.yudao.module.scrm.dal.mysql.clue.ClueMapper;
import cn.iocoder.yudao.module.scrm.dal.mysql.customer.CustomerMapper;
import cn.iocoder.yudao.module.scrm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.scrm.service.customer.CustomerService;
import cn.iocoder.yudao.module.scrm.util.CheckNoGeneratorUtil;
import cn.iocoder.yudao.module.scrm.util.PhoneNumberUtil;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.scrm.enums.DictTypeConstants.*;
import static cn.iocoder.yudao.module.scrm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.scrm.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.scrm.enums.clue.ScrmClueStateEnum.isClient;
import static cn.iocoder.yudao.module.scrm.enums.clue.ScrmClueStateEnum.isInvalid;


/**
 * 线索 Service 实现类
 *
 * @author admin
 */
@Service
@Validated
public class ClueServiceImpl implements ClueService {


    @Resource
    private ClueMapper clueMapper;

    @Resource
    private CheckNoGeneratorUtil checkNoGeneratorUtil;

    @Resource
    private AdminUserApi adminUserApi;

    @Resource
    private DeptApi deptApi;

    @Resource
    private CustomerMapper customerMapper;

    @Resource
    private DictDataApi dictDataApi;

    @Resource
    private CustomerService customerService;

    @Override
    @LogRecord(type = SCRM_CLUE_TYPE, subType = SCRM_CLUE_CREATE_SUB_TYPE, bizNo = "{{#newClueId}}", success = SCRM_CLUE_CREATE_SUCCESS)
    public Integer createClue(ClueSaveReqVO clueRequest) {
        // 1. 获取当前登录用户数据
        AdminUserRespDTO currentUser = adminUserApi.getUserNotCheckDataPermission(getLoginUserId()).getCheckedData();

        // 通过 Optional 简化 Null 检查，获取部门数据
        DeptRespDTO department = Optional.ofNullable(currentUser.getDeptId())
                .map(deptApi::getDeptNotCheckDataPermission)
                .map(CommonResult::getCheckedData).orElse(null);

        // 2. 设置系统默认值
        ClueDO newClue = BeanUtils.toBean(clueRequest, ClueDO.class);
        setDefaultValues(newClue, currentUser, department);

        // 3. 插入数据
        clueMapper.insert(newClue);

        // 4. 记录操作日志上下文
        LogRecordContext.putVariable("newClueId", newClue.getId());
        LogRecordContext.putVariable("clueName", clueRequest.getName());
        LogRecordContext.putVariable("userName", currentUser.getNickname());

        // 5. 返回生成的线索ID
        return newClue.getId();
    }


    @LogRecord(type = SCRM_CLUE_TYPE, subType = SCRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#updateRequest.id}}", success = SCRM_CLUE_UPDATE_SUCCESS)
    @Override
    public void updateClue(ClueSaveReqVO updateRequest) {
        // 验证线索是否存在
        ClueDO existingClue = Optional.ofNullable(Long.valueOf(updateRequest.getId()))
                .map(this::validateCluesExists).orElseThrow(() -> exception(CLUE_NOT_EXISTS));

        updateRequest.setPhone(PhoneNumberUtil.isUpdatePhoneNumber(updateRequest.getPhone(), existingClue.getPhone()));

        // 更新线索信息
        ClueDO updatedClue = BeanUtils.toBean(updateRequest, ClueDO.class);
        clueMapper.updateById(updatedClue);

        // 记录操作日志
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(existingClue, ClueSaveReqVO.class));
        LogRecordContext.putVariable("clueName", existingClue.getClueSource());

        // 获取并记录当前用户的昵称
        Optional.ofNullable(SecurityFrameworkUtils.getLoginUser())
                .map(loginUser -> adminUserApi.getUserNotCheckDataPermission(loginUser.getId()).getCheckedData())
                .map(AdminUserRespDTO::getNickname)
                .ifPresent(nickname -> LogRecordContext.putVariable("userName", nickname));
    }

    @Override
    public void deleteClue(Integer id) {
        // 校验存在
        validateCluesExists(Long.valueOf(id));
        // 删除
        clueMapper.deleteById(id);
    }

    @Override
    public ClueDO getClue(Integer id) {
        return clueMapper.selectById(id);
    }

    @Override
    public ClueDO getDetail(Integer id) {
        return  clueMapper.selectById(id);
    }

    @Override
    @EncryptPhoneNumbers
    public PageResult<ClueDO> getCluePage(CluePageReqVO pageReqVO, Integer tenantId) {
        // 获取线索分页结果
        PageResult<ClueDO> clueDoPageResult = clueMapper.selectPage(pageReqVO);

        // 创建字典上下文对象
        DictTenantContext tenantContext = new DictTenantContext(tenantId);

        // 直接更新线索列表中的字典值
        clueDoPageResult.getList().forEach(clueDO -> {
            clueDO.setClientSource(Optional.ofNullable(clueDO.getClientSource())
                    .map(clientSource -> tenantContext.getDictDataLabel(SCRM_CLUE_CLIENT, clientSource)).orElse(clueDO.getClientSource()));

            clueDO.setInfoSource(Optional.ofNullable(clueDO.getInfoSource())
                    .map(infoSource -> tenantContext.getDictDataLabel(SCRM_CLUE_INFO, infoSource)).orElse(clueDO.getInfoSource()));

            clueDO.setClientQuality(Optional.ofNullable(clueDO.getClientQuality())
                    .map(clientQuality -> tenantContext.getDictDataLabel(SCRM_CLUE_IDEA, clientQuality)).orElse(clueDO.getClientQuality()));

            clueDO.setClueState(Optional.ofNullable(clueDO.getClueState())
                    .map(clueState -> DictFrameworkUtils.getDictDataLabel(SCRM_CLUE_STATE, clueState)).orElse(clueDO.getClueState()));
        });

        //直接返回
        return clueDoPageResult;
    }

    @Override
    @EncryptPhoneNumbers
    public PageResult<ClueDO> getCluePageExport(CluePageReqVO pageReqVO) {
        return clueMapper.selectPage(pageReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ClueImportExcelRespVO importClueList(List<ClueImportExcelVO> importClues, boolean isUpdateSupport) {
        // 0. 非空校验
        Optional.ofNullable(importClues).filter(clues -> !clues.isEmpty()).orElseThrow(() ->  exception(CLUE_IMPORT_NOT_IS_EMPTY));
        // 0.1. 初始化返回响应对象
        ClueImportExcelRespVO respVO = ClueImportExcelRespVO.builder()
                .createClueNames(new ArrayList<>())
                .updateClueNames(new ArrayList<>())
                .failureClueNames(new LinkedHashMap<>())
                .numsCount(new LinkedHashMap<>())
                .build();

        // 1. 获取当前登录用户数据
        AdminUserRespDTO userRespDTO = adminUserApi.getUserNotCheckDataPermission(getLoginUserId()).getCheckedData();
        String userName = userRespDTO.getNickname();
        // 1.1 通过 Optional 简化 Null 检查，获取部门数据
        DeptRespDTO deptRespDTO = Optional.ofNullable(userRespDTO.getDeptId())
                .map(deptApi::getDeptNotCheckDataPermission)
                .map(CommonResult::getCheckedData).orElse(null);

        // 2. 处理每一条导入记录
        importClues.forEach(clueImportExcelVO -> {
            try {
                // 校验，姓名和手机号码为空就抛错误
                validateClueForCreate(clueImportExcelVO);

                // 判断数据中是否存在
                ClueDO existClue = clueMapper.selectOne(ClueDO::getPhone, clueImportExcelVO.getPhone());

                if (existClue == null) {
                    // 不存在就新增
                    ClueDO clue = BeanUtils.toBean(clueImportExcelVO, ClueDO.class).setImportCheck("1");//并且设置类型为导入的数据
                    // 默认值
                    this.setDefaultValues(clue, userRespDTO, deptRespDTO);

                    clueMapper.insert(clue);
                    respVO.getCreateClueNames().add(String.format("导入手机号为：%s 的线索成功", clue.getPhone()));
                    getSelf().importInsertClueLog(clueImportExcelVO, userName, clue);
                } else {
                    // 存在的话，允许更新就更新，不允许更新就抛出错误
                    if (!isUpdateSupport) {
                        respVO.getFailureClueNames().put(clueImportExcelVO.getPhone(),
                                StrUtil.format(CLUE_NAME_EXISTS.getMsg(), clueImportExcelVO.getPhone()));
                    } else {
                        ClueDO updateClue = BeanUtils.toBean(clueImportExcelVO, ClueDO.class).setId(existClue.getId());
                        clueMapper.updateById(updateClue);
                        respVO.getUpdateClueNames().add(String.format("修改手机号为：%s的线索成功", existClue.getPhone()));
                        getSelf().importUpdateClueLog(clueImportExcelVO, userName, existClue, existClue.getId());
                    }
                }
            } catch (ServiceException ex) {
                respVO.getFailureClueNames().put(String.format("Excel第%d行报错了：", importClues.indexOf(clueImportExcelVO)), ex.getMessage());            }
        });

        // 3. 记录导入结果并返回
        return recordImportResults(respVO);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public ClueImportExcelRespVO importClueListNotCanUpdate(List<ClueImportExcelVO> importClues) {
        // 0. 非空校验，确保导入的数据不为空
        validateImportClues(importClues);

        // 1. 校验数据有效性，返回包含成功与失败的结果
        ClueImportExcelRespVO resultMap = validateClueForCreate(importClues);

        // 2. 获取当前登录用户信息及其部门信息
        AdminUserRespDTO userRespDTO = adminUserApi.getUserNotCheckDataPermission(getLoginUserId()).getCheckedData();
        String userName = userRespDTO.getNickname();
        DeptRespDTO deptRespDTO = getDeptRespDTO(userRespDTO);

        // 3. 转换导入的 Excel 数据并设置默认值
        List<ClueDO> cluesToInsert = BeanUtils.toBean(importClues, ClueDO.class, clue -> {
            // 设置为导入的数据
            clue.setImportCheck("1");
            // 设置默认值
            setDefaultValues(clue, userRespDTO, deptRespDTO);
        });
        // 记录导入数据总数
        resultMap.getNumsCount().put("sumCount", cluesToInsert.size());
        // 4. 批量插入数据并记录日志结果
        insertCluesAndLog( importClues,resultMap, cluesToInsert, userName);

        // 5. 返回导入结果
        return recordImportResultsNotCanUpdate(resultMap);
    }

    private void validateImportClues(List<ClueImportExcelVO> importClues) {
        // 校验导入数据是否为空
        Optional.ofNullable(importClues)
                .filter(clues -> !clues.isEmpty())
                .orElseThrow(() -> exception(CLUE_IMPORT_NOT_IS_EMPTY));  // 异常抛出
    }

    private DeptRespDTO getDeptRespDTO(AdminUserRespDTO userRespDTO) {
        // 通过部门 ID 获取部门信息
        return Optional.ofNullable(userRespDTO.getDeptId())
                .map(deptApi::getDeptNotCheckDataPermission)
                .map(CommonResult::getCheckedData)
                .orElse(null);  // 如果部门为空则返回 null
    }

    private void insertCluesAndLog(List<ClueImportExcelVO> importClues, ClueImportExcelRespVO resultMap, List<ClueDO> cluesToInsert, String userName) {
        // 批量插入数据并记录日志
        if (!cluesToInsert.isEmpty() && resultMap.getFailureClueNames().isEmpty()) {  // 确保数据非空且无失败信息
            boolean insertResult = clueMapper.insertBatch(cluesToInsert);  // 执行批量插入
            if (insertResult) {
                // 插入成功，记录日志
                for (int i = 0; i < cluesToInsert.size(); i++) {
                    getSelf().importInsertClueLogByNotCanUpdate(importClues.get(i),userName, cluesToInsert.get(i));  // 记录每条线索的导入日志
                    resultMap.getCreateClueNames().add(String.format("导入手机号为 %s 的线索成功", cluesToInsert.get(i).getPhone()));  // 记录成功的线索信息
                }
            }
        }
    }



    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean clueChangeState(Set<Long> ids, String newStateValue) {
        // 根据提供的ID集合批量查询线索数据
        List<ClueDO> clues = clueMapper.selectBatchIds(ids);

        // 如果查询结果为空，抛出异常
        if (CollUtil.isEmpty(clues)) {
            throw exception(CLUE_SELECT_IDS);
        }

        // 获取已经转为客户的线索名称
        List<String> transformedClueNames = getAlreadyTransformedClues(clues);

        // 如果有线索已经转为客户，构建错误信息并抛出异常
        if (!transformedClueNames.isEmpty()) {
            String errorMessage = StrUtil.format(CLUE_OPERATION_FAIL_ALREADY.getMsg(), String.join(" ， ", transformedClueNames));
            throw exception(new ErrorCode(500, errorMessage));
        }

        // 获取新状态的字典标签
        String newStateLabel = DictFrameworkUtils.getDictDataLabel(SCRM_CLUE_STATE, newStateValue);

        // 构建状态值到标签的映射
        Map<String, String> stateLabelMap = buildStateLabelMap(dictDataApi.getDictDataList(SCRM_CLUE_STATE).getCheckedData());

        // 更新每个线索的状态并记录状态变化
        clues.forEach(clue -> updateClueState(clue, newStateValue, stateLabelMap, newStateLabel));

        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean clueTransferAffiliation(ClueTransferAffiliationVo clueTransferAffiliationVo, Long userId) {
        // 获取线索的姓名集合
        List<String> alreadyTransformedClues = clueTransferAffiliationVo.getIds().stream().map(this::validateCluesExists)
                .filter(clue -> isClient(clue.getClueState()) && clue.getCustomerId() != null).map(ClueDO::getName).collect(Collectors.toList());

        List<String> invalidTransformedClues = clueTransferAffiliationVo.getIds().stream().map(this::validateCluesExists)
                .filter(clue -> isInvalid(clue.getClueState())).map(ClueDO::getName).collect(Collectors.toList());

        // 构建错误信息
        String clientMsg = !alreadyTransformedClues.isEmpty() ? StrUtil.format(CLUE_OPERATION_FAIL_ALREADY.getMsg(), String.join(" ， ", alreadyTransformedClues)) : "";

        String invalidMsg = !invalidTransformedClues.isEmpty() ? StrUtil.format(CLUE_INVALID_FAIL_ALREADY.getMsg(), String.join(" ， ", invalidTransformedClues)) : "";

        String message = clientMsg + invalidMsg;
        //有无效线索和转移为客户的线索就抛异常
        if (!message.isEmpty()) {
            throw exception(new ErrorCode(500, message));
        }
        //企微id转为用户id
        Long newUserId = adminUserApi.getLeaderByWxIdNotCheckDataPermission(clueTransferAffiliationVo.getNewOwnerUserId(), clueTransferAffiliationVo.getTencentId()).getData().getId();

        for (Long id : clueTransferAffiliationVo.getIds()) {
            // 1 校验线索是否存在
            ClueDO clue = validateCluesExists(id);

            // 1.1 获取之前的线索归属人-部门
            String oldPerson = (clue.getCluePerson() != null) ? clue.getCluePerson() : "";
            String oldDept = (clue.getClueDepartment() != null) ? clue.getClueDepartment() : "";

            // 1.2 获取新的线索归属人-部门
            AdminUserRespDTO userRespDTO = adminUserApi.getUserNotCheckDataPermission(newUserId).getCheckedData();

            // 1.1 使用 Optional 简化 Null 检查，获取部门数据
            DeptRespDTO deptRespDTO = Optional.ofNullable(userRespDTO.getDeptId()).map(deptApi::getDeptNotCheckDataPermission).map(CommonResult::getCheckedData).orElse(null);

            // 获取新的归属人
            String newCluePerson = userRespDTO.getNickname();

            // 使用 Optional 处理部门数据
            String deptId = Optional.ofNullable(userRespDTO.getDeptId()).map(String::valueOf).orElse("");
            String newClueDept = Optional.ofNullable(deptRespDTO).map(DeptRespDTO::getName).filter(name -> !name.isEmpty()).orElse("空");

            // 2.1 设置新的负责人-和前归属人-
            clueMapper.updateById(new ClueDO().setId(Math.toIntExact(id)).setCluePerson(newCluePerson)
                    .setClueDepartment(newClueDept).setUserId(String.valueOf(userRespDTO.getId()))
                    .setDeptId(deptId).setAgoAffiliationBy(oldPerson).setAgoAffiliationDepartment(oldDept)
                    .setRemark(clueTransferAffiliationVo.getRemark()));

            // 3. 记录转移日志
            getSelf().recordClueTransferLog(id, clue.getName(), oldPerson, newCluePerson, clueTransferAffiliationVo.getRemark(), clue.getRemark());

        }
        return true;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean clueTransferClient(ClueTransferClientVo transferClientVo, Long userId) {
        // 获取新的用户ID
        Long newUserId = adminUserApi.getLeaderByWxIdNotCheckDataPermission(String.valueOf(transferClientVo.getOwnerUserId()), transferClientVo.getTencentId())
                .getData().getId();
        transferClientVo.setOwnerUserId(String.valueOf(newUserId));

        // 处理线索并获取错误信息
        String errorMessage = processCluesAndGetErrorMessage(transferClientVo);

        // 如果有无效或已转移的线索，则抛出异常
        if (!errorMessage.isEmpty()) {
            throw exception(new ErrorCode(500, errorMessage));
        }

        // 将有效线索转移为客户
        List<ClueDO> clues = transferClientVo.getClueIds().stream()
                .map(this::validateCluesExists)
                .collect(Collectors.toList());

        clues.stream()
                .filter(clue -> !isClient(clue.getClueState()) && !isInvalid(clue.getClueState()))
                .forEach(clue -> {
                    // 创建并设置客户对象
                    CustomerDO customer = createCustomerFromClue(clue, transferClientVo);

                    // 插入客户信息到数据库
                    customerMapper.insert(customer);

                    // 更新线索状态为客户
                    clueMapper.updateById(new ClueDO().setId(Math.toIntExact(clue.getId()))
                            .setCustomerId(customer.getId())
                            .setClueState("2"));

                    // 记录操作日志
                    getSelf().clueTransferClientDetails(Long.valueOf(clue.getId()), clue.getName());
                });

        return true;
    }


    @Override
    @LogRecord(type = SCRM_CLUE_TYPE, subType = SCRM_CLUE_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}}", success = SCRM_CLUE_FOLLOW_UP_SUCCESS)
    //@CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
    public void updateClueFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) {
        // 校验线索是否存在
        ClueDO oldClue = validateCluesExists(id);

        // 更新线索
        clueMapper.updateById(new ClueDO().setId(id.intValue()).setFollowUpTime(contactNextTime));

        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("clueName", oldClue.getClueSource());
    }

    /**
     * 获得自身的代理对象，解决日志记录在 AOP操作下对于this.方法生效问题
     *
     * @return 自己
     */
    private ClueServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }




    /**
     * 记录导入线索时新增的操作日志
     *
     * @param
     */
    @LogRecord(type = SCRM_CLUE_TYPE, subType = SCRM_CLUE_IMPORT_CREATE_SUB_TYPE, bizNo = "{{#clueId}}", success = SCRM_CLUE_IMPORT_CREATE_SUCCESS)
    public void importInsertClueLog(ClueImportExcelVO clueImportExcelVO, String userName, ClueDO newClue) {
        LogRecordContext.putVariable("userName", userName);
        LogRecordContext.putVariable("clueName", newClue.getName());
        LogRecordContext.putVariable("clueId", newClue.getId());
    }

    /**
     * 记录导入线索时新增的操作日志
     *
     * @param
     */
    @LogRecord(type = SCRM_CLUE_TYPE, subType = SCRM_CLUE_IMPORT_CREATE_SUB_TYPE, bizNo = "{{#clueId}}", success = SCRM_CLUE_IMPORT_CREATE_SUCCESS)
    public void importInsertClueLogByNotCanUpdate(ClueImportExcelVO clueImportExcelVO,String userName, ClueDO clueDO) {
        LogRecordContext.putVariable("userName", userName);
        LogRecordContext.putVariable("clueName", clueDO.getName());
        LogRecordContext.putVariable("clueId", clueDO.getId());
    }

    /**
     * 记录导入线索时修改的操作日志-
     *
     * @param
     */
    @LogRecord(type = SCRM_CLUE_TYPE, subType = SCRM_CLUE_IMPORT_UPDATE_SUB_TYPE, bizNo = "{{#clueId}}", success = SCRM_CLUE_IMPORT_UPDATE_SUCCESS)
    public void importUpdateClueLog(ClueImportExcelVO clueImportExcelVO, String userName, ClueDO oldClue, Integer clueId) {
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, ClueImportExcelVO.class));
        LogRecordContext.putVariable("userName", userName);
        LogRecordContext.putVariable("clueName", oldClue.getName());

    }




    /**
     * 设置线索对象的系统默认值。
     *
     * @param clue       需要设置默认值的线索对象。
     * @param user       当前登录用户的信息，用于设置创建者和用户ID。
     * @param department 当前用户所在部门的信息，用于设置部门ID和部门的名称。
     *
     *                   <p>此方法执行以下操作：</p>
     *                   <ul>
     *                       <li>为线索对象生成并设置唯一的系统编号。</li>
     *                       <li>设置线索对象的用户ID为当前登录用户的ID。</li>
     *                       <li>设置线索对象的创建者和线索负责人姓名为当前登录用户的昵称。</li>
     *                       <li>如果部门信息不为空，设置线索对象的部门ID为当前用户的部门ID，并设置部门名称。如果部门名称为空，则设为"空"。</li>
     *                   </ul>
     */
    private void setDefaultValues(ClueDO clue, AdminUserRespDTO user, DeptRespDTO department) {
        clue.setSystemNumber(checkNoGeneratorUtil.generateUniqueCheckNo(CrmBizTypeEnum.CRM_CLUE.getType())).setUserId(String.valueOf(user.getId())).setCreateBy(user.getNickname()).setCluePerson(user.getNickname());

        // 设置部门信息
        Optional.ofNullable(department).ifPresent(dept -> clue.setDeptId(String.valueOf(user.getDeptId())).setClueDepartment(Optional.ofNullable(dept.getName()).filter(name -> !name.isEmpty()).orElse("空")));
    }

    /**
     * 验证线索是否存在。如果不存在，抛出异常。
     *
     * @param id 线索ID
     * @return 线索DO对象
     */
    private ClueDO validateCluesExists(Long id) {
        return Optional.ofNullable(clueMapper.selectById(id)).orElseThrow(() -> exception(CLUE_NOT_EXISTS));
    }

    /**
     * 校验导入的数据中姓名和手机号不能为空
     *
     * @param importClue 导入的数据
     */
    private void validateClueForCreate(ClueImportExcelVO importClue) {
        if (StrUtil.isEmptyIfStr(importClue.getPhone()) && StrUtil.isEmptyIfStr(importClue.getName())) {
            throw exception(CLUE_IMPORT_PHONE_NAME_NOT_IS_EMPTY);
        }
        // 校验线索姓名不能为空
        if (StrUtil.isEmptyIfStr(importClue.getName())) {
            throw exception(CLUE_IMPORT_NAME_NOT_IS_EMPTY);
        }
        if (StrUtil.isEmptyIfStr(importClue.getPhone())) {
            throw exception(CLUE_IMPORT_PHONE_NOT_IS_EMPTY);
        }
    }

    /**
     * 批量-校验导入的数据中姓名和手机号不能为空
     *
     * @param importClues 导入的数据
     */
    @Transactional(rollbackFor = Exception.class)
    public ClueImportExcelRespVO validateClueForCreate(List<ClueImportExcelVO> importClues ){
        // 0.1. 初始化返回响应对象
        ClueImportExcelRespVO respVO = ClueImportExcelRespVO.builder()
                .createClueNames(new ArrayList<>())
                .updateClueNames(new ArrayList<>())
                .failureClueNames(new LinkedHashMap<>())
                .numsCount(new LinkedHashMap<>())
                .build();

        // 1. 收集所有手机号码
        Set<String> phoneNumbers = importClues.stream()
                .map(ClueImportExcelVO::getPhone)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        // 2. 批量查询数据库中已有的手机号码
        LambdaQueryWrapper<ClueDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ClueDO::getPhone, phoneNumbers);

        Map<String, ClueDO> existingClues = clueMapper.selectList(queryWrapper)
                .stream()
                .collect(Collectors.toMap(
                        ClueDO::getPhone,  // 使用 phone 作为键
                        clue -> clue,      // 使用 clue 作为值
                        (existing, replacement) -> existing // 如果有重复键，保留第一个 ClueDO 对象
                ));

        // 3. 处理每一条导入记录
        for (int i = 0; i < importClues.size(); i++) {
            ClueImportExcelVO clueImportExcelVO = importClues.get(i);
            try {
                // 校验，姓名和手机号码为空则抛出异常
                validateClueForCreate(clueImportExcelVO);

                // 检查手机号是否已存在
                ClueDO existClue = existingClues.get(clueImportExcelVO.getPhone());
                if (existClue != null) {
                    String userId = existClue.getUserId();
                    String nickname = adminUserApi.getUserNotCheckDataPermission(Long.valueOf(userId)).getCheckedData().getNickname();
                    if (!userId.isEmpty()) {
                        throw exception(CLUE_IMPORT_PHONE_EXIST_BY_OTHER_PEOPLE, existClue.getPhone(), nickname);
                    }
                }
            } catch (ServiceException ex) {
                // 记录失败的条目及其错误信息
                respVO.getFailureClueNames().put(String.format("Excel第%d行报错了：", i + 1), ex.getMessage());
            }
        }
        return respVO;
    }


    /**
     * 计算和设置导入结果的统计信息
     * @param respVO 结果响应对象
     */
    private ClueImportExcelRespVO recordImportResults(ClueImportExcelRespVO respVO) {
        // 计算创建、更新和失败的记录数
        int createCount = respVO.getCreateClueNames().size();
        int updateCount = respVO.getUpdateClueNames().size();
        int failCount = respVO.getFailureClueNames().size();

        // 使用流式操作来计算统计数据
        respVO.getNumsCount().put("sumCount", createCount + updateCount + failCount);
        respVO.getNumsCount().put("succeedCount", createCount + updateCount);
        respVO.getNumsCount().put("failingCount", failCount);
        return respVO;
    }


    /**
     * 计算和设置导入结果的统计信息
     * @param respVO 结果响应对象
     */
    private ClueImportExcelRespVO recordImportResultsNotCanUpdate(ClueImportExcelRespVO respVO) {
        // 计算创建、更新和失败的记录数
        int createCount = respVO.getCreateClueNames().size();
        int failCount = respVO.getFailureClueNames().size();


        respVO.getNumsCount().put("succeedCount", createCount);
        respVO.getNumsCount().put("failingCount", failCount);
        return respVO;
    }


    /**
     * 从线索列表中过滤出已经转为客户的线索，并返回这些线索的名称
     *
     * @param list 线索列表
     * @return 已转为客户的线索名称列表
     */
    private List<String> getAlreadyTransformedClues(List<ClueDO> list) {
        return list.stream().filter(clueDO -> isClient(clueDO.getClueState())) // 筛选已转为客户的线索
                .map(ClueDO::getName) // 提取线索名称
                .collect(Collectors.toList()); // 收集到列表中
    }


    /**
     * 构建状态值到状态标签的映射
     *
     * @param dictDataList 状态字典数据列表
     * @return 状态值到状态标签的映射
     */
    private Map<String, String> buildStateLabelMap(List<DictDataRespDTO> dictDataList) {
        return dictDataList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));
    }

    /**
     * 转为无效-更新线索的状态，并记录状态变化日志
     *
     * @param clueDO        当前线索对象
     * @param newStateValue 新状态值
     * @param stateLabelMap 状态值到标签的映射
     * @param newLabelState 新状态标签
     */
    private void updateClueState(ClueDO clueDO, String newStateValue, Map<String, String> stateLabelMap, String newLabelState) {
        // 获取旧状态的标签
        String oldLabelState = stateLabelMap.getOrDefault(clueDO.getClueState(), "");

        // 如果旧状态与新状态不同，则更新线索状态
        if (!oldLabelState.equals(newLabelState)) {
            clueDO.setClueState(newStateValue);
            clueMapper.updateById(clueDO);

            // 记录状态变化日志
            getSelf().clueChangeStateDetails(Long.valueOf(clueDO.getId()), clueDO, clueDO.getName(), oldLabelState, newLabelState);
        }
    }

    /**
     * 记录线索状态变化的详细日志
     *
     * @param id            线索ID
     * @param clueDO        线索对象
     * @param clueName      线索名称
     * @param oldLabelState 旧状态标签
     * @param newLabelState 新状态标签
     */
    @LogRecord(type = SCRM_CLUE_TYPE, subType = SCRM_CLUE_CHANGE_STATE_TYPE, bizNo = "{{#id}}", success = SCRM_CLUE_CHANGE_STATE_SUCCESS)
    public void clueChangeStateDetails(Long id, ClueDO clueDO, String clueName, String oldLabelState, String newLabelState) {
        // 记录日志上下文信息
        LogRecordContext.putVariable("clueName", clueName);
        LogRecordContext.putVariable("oldLabelState", oldLabelState);
        LogRecordContext.putVariable("newLabelState", newLabelState);
    }

    //转移线索日志操作
    @LogRecord(type = SCRM_CLUE_TYPE, subType = SCRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#clueId}}", success = SCRM_CLUE_TRANSFER_SUCCESS)
    public void recordClueTransferLog(Long clueId, String clueName, String oldPerson, String newCluePerson, String newRemark, String oldRemark) {
        // 设置日志记录上下文变量
        LogRecordContext.putVariable("clueId", clueId);
        LogRecordContext.putVariable("clue", new ClueDO().setId(Math.toIntExact(clueId)).setName(clueName));
        LogRecordContext.putVariable("oldPerson", oldPerson);
        LogRecordContext.putVariable("newCluePerson", newCluePerson);
        LogRecordContext.putVariable("newRemark", newRemark);
        LogRecordContext.putVariable("oldRemark", oldRemark);

    }


    /**
     * 从线索和转移客户端数据创建客户对象
     * @param clue 线索数据
     * @param transferClientVo 转移客户端数据
     * @return 创建的客户对象
     */
    private CustomerDO createCustomerFromClue(ClueDO clue, ClueTransferClientVo transferClientVo) {
        CustomerDO customer = new CustomerDO();

        // 处理 String 类型字段
        customer.setName(Optional.ofNullable(clue.getName()).orElse(""));
        customer.setEnterprise(Optional.ofNullable(clue.getEnterprise()).orElse(""));
        customer.setPhone(Optional.ofNullable(clue.getPhone()).orElse(""));
        customer.setClueSource(Optional.ofNullable(clue.getClueSource()).orElse(""));
        // 处理 LocalDateTime 类型字段
        customer.setCustomerSourceTime(Optional.of(clue.getAcquisitionTime()).orElse(null));
        // 处理 String 类型字段
        customer.setCustomerSource(Optional.ofNullable(clue.getClientSource()).orElse(""));
        customer.setMaterialSource(Optional.ofNullable(clue.getMaterialSource()).orElse(""));
        customer.setInfoSource(Optional.ofNullable(clue.getInfoSource()).orElse(""));
        customer.setRemark(Optional.ofNullable(clue.getRemark()).orElse(""));
        customer.setClientQuality(Optional.ofNullable(clue.getClientQuality()).orElse(""));
        // 处理 Integer 类型字段
        customer.setLevel(Optional.of(transferClientVo.getLevel()).orElse(null));
        // 处理 Long 类型字段
        customer.setOwnerUserId(Optional.ofNullable(transferClientVo.getOwnerUserId())
                .map(Long::valueOf)
                .orElse(null));
        return customer;
    }




    /**
     * 构建转移线索时的错误信息
     * @param transferClientVo 转移线索的请求数据
     * @return  错误信息
     */
    private String processCluesAndGetErrorMessage(ClueTransferClientVo transferClientVo) {

        //校验转移数量
        //校验负责人的私库容量是否超过上限
        customerService.checkCapacityRule(Long.valueOf(transferClientVo.getOwnerUserId()),transferClientVo.getClueIds().size());


        // 通过流处理线索并分类
        List<ClueDO> clues = transferClientVo.getClueIds().stream()
                .map(this::validateCluesExists)
                .collect(Collectors.toList());

        // 过滤并获取已转移线索的名称
        List<String> alreadyTransformedClues = clues.stream()
                .filter(clue -> isClient(clue.getClueState()) && clue.getCustomerId() != null)
                .map(ClueDO::getName)
                .collect(Collectors.toList());

        // 过滤并获取无效线索的名称
        List<String> invalidTransformedClues = clues.stream()
                .filter(clue -> isInvalid(clue.getClueState()))
                .map(ClueDO::getName)
                .collect(Collectors.toList());

        // 校验手机号码在客户表中是否存在，有两种情况.
        // 一种是存在但是该客户没有负责人，就提示报错手机号码为xxxx的线索已经存在公海中
        // 一种是存在并且该客户有负责人，  "手机号为:【{}】 的线索在客户中已经存在，负责人是：【{}】 ");
        Map<String, String> existPublicCustomerMap = new HashMap<>(); // 无负责人的手机号
        Map<String, String> existCustomerWithOwnerMap = new HashMap<>(); // 有负责人的手机号

        clues.forEach(clueDO -> {
            CustomerDO customerDO = customerMapper.selectOne(CustomerDO::getPhone, clueDO.getPhone());
            if (customerDO != null) {
                if (customerDO.getOwnerUserId() != null) {
                    // 存在客户且有负责人
                    String nickname = adminUserApi.getUserNotCheckDataPermission(customerDO.getOwnerUserId())
                            .getCheckedData()
                            .getNickname();
                    existCustomerWithOwnerMap.put(clueDO.getPhone(), nickname);
                } else {
                    // 存在客户但无负责人
                    existPublicCustomerMap.put(clueDO.getPhone(), clueDO.getName());
                }
            }
        });



        // 构建错误信息
        String clientMsg = !alreadyTransformedClues.isEmpty()
                ? StrUtil.format(CLUE_TRANSFORM_FAIL_ALREADY.getMsg(), String.join(" ， ", alreadyTransformedClues))
                : "";

        String invalidMsg = !invalidTransformedClues.isEmpty()
                ? StrUtil.format(CLUE_INVALID_FAIL_ALREADY.getMsg(), String.join(" ， ", invalidTransformedClues))
                : "";

        String publicCustomerMsg = !existPublicCustomerMap.isEmpty()
                ? StrUtil.format(CLUE_TRANSFORM_EXIST_PUBLIC_CUSTOMER_PHONE.getMsg(),
                String.join(" ， ", existPublicCustomerMap.keySet()))
                : "";

        String existingCustomerMsg = !existCustomerWithOwnerMap.isEmpty()
                ? existCustomerWithOwnerMap.entrySet().stream()
                .map(entry -> StrUtil.format(CLUE_TRANSFORM_EXIST_CUSTOMER_PHONE.getMsg(),
                        entry.getKey(), entry.getValue()))
                .collect(Collectors.joining(" ， "))
                : "";

        // 组合所有非空消息，并在消息之间添加逗号
        String resultMsg = Stream.of(clientMsg, invalidMsg, publicCustomerMsg, existingCustomerMsg)
                .filter(msg -> !msg.isEmpty())
                .collect(Collectors.joining(" ， "));

        return resultMsg;
    }


    //将线索对应的值转换成客户
    @LogRecord(type = SCRM_CLUE_TYPE, subType = SCRM_CLUE_TRANSLATE_SUB_TYPE, bizNo = "{{#id}}", success = SCRM_CLUE_TRANSLATE_SUB_SUCCESS)
    public void clueTransferClientDetails(Long id, String clueName) {
        // 记录日志上下文信息
        LogRecordContext.putVariable("clueName", clueName);
    }





}